
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <script>
        // 期约是一个有状态的对象,可能处于如下3种状态之一:
        //  1. 待定(pending)  
        //  2. 兑现(fulfilled, 有时候也称为解决[resolved] )
        //  3. 拒绝(rejected)
        // * 待定(pending)是期约的最初始状态。在待定状态下,期约可以设定(settled)为代表成功的兑现
        //   (fulfilled)状态, 或者代表失败的拒绝(rejected)状态。无论设定为哪种状态都是不可逆的。
        // * 只要从待定(pending)转换为兑现(fulfilled)或拒绝(rejected), 期约的状态就不再改变。
        //   而且,也不能保证期约必然会脱离待定状态。
        //   因此, 组织合理的代码无论期约解决(resolve)还是拒绝(reject), 甚至永远处于待定(pending)状态,
        //   都应该具有恰当的行为。
        // * 期约的状态是私有的, 不能直接通过JavaScript检测到。这主要是为了避免根据读取到的期约状态, 
        //   以同步方式处理期约对象。另外, 期约的状态也不能被外部JavaScript代码修改。这与不能读取该
        //   状态的原因是一样的: 期约故意将异步行为封装起来,从而隔离外部的同步代码。
        // *****************************************************************
        // * 期约主要有两大用途。首先是抽象地表示一个异步操作。期约的状态代表期约是否完成。"待定(pending)"
        //   表示尚未开始或者正在执行中。"兑现(fulfilled)"表示已经成功完成, 而"拒绝(rejected)"则表示
        //   没有成功完成。
        // * 某些情况下, 这个状态机就是期约可以提供的最有用的信息。知道一段异步代码已经完成, 对于其他代码
        //   而言已经足够了。比如, 假设期约要向服务器发送一个HTTP请求。请求返回200~299范围内的状态码就
        //   足以让期约的状态变为"兑现"。类似地, 如果请求返回的状态码不在200~299这个范围内, 那么就会把
        //   期约状态切换为"拒绝"。
        // * 在另外一些情况下, 期约封装的异步操作会实际生成某个值, 而程序期待期约状态改变时可以访问这个值。
        //   相应地, 如果期约被拒绝, 程序就会期待期约状态改变时可以拿到拒绝的理由。比如, 假设期约向服务器
        //   发送一个HTTP请求并预定会返回一个JSON。如果请求返回范围在200~299的状态码, 则足以让期约的状态
        //   变为兑现。此时期约内部就可以收到一个JSON字符串。类似地, 如果请求返回的状态码不在200~299这个
        //   范围内, 那么就会把期约状态切换为拒绝。此时拒绝的理由可能是一个Error对象, 包含着HTTP状态码及
        //   相关错误消息。
        // * 为了支持这两种用例, 每个期约只要状态切换为兑现(fulfilled), 就会有一个私有的内部值(value)。
        //   类似地, 每个期约只要状态切换为拒绝(rejected), 就会有一个私有的内部理由(reason)。无论是值
        //   还是理由, 都是包含原始值或对象的不可修改的引用。二者都是可选的, 而且默认值为undefined。在
        //   期约到达某个落定状态时执行的异步代码始终会收到这个值或理由。 
        // *****************************************************************
        // * 由于期约的状态是私有的, 所以只能在内部进行操作。内部操作在期约的执行器函数中完成。执行器函数主要
        //   有两项职责:(1)初始化期约的异步行为和(2)控制状态的最终转换。其中, 控制期约状态的转换是通过调用它
        //   的两个函数参数实现的。这两个函数参数通常都命名为resolve()和reject()。调用resolve()会把状态切换为
        //   兑现, 调用reject()会把状态切换为拒绝。另外, 调用 reject()也会抛出错误。
        let p1 = new Promise((resolve, reject) => resolve());
        setTimeout(console.log, 0, p1);  // Promise <resolved> 

        let p2 = new Promise((resolve, reject) => reject());
        setTimeout(console.log, 0, p2);   // Promise <rejected> 
        // Uncaught error (in promise) 

        ///////////////////////////////////////////////////////////        
        // 在前面的例子中, 并没有什么异步操作, 因为在初始化期约时, 执行器函数已经改变了每个期约的
        // 状态。这里的关键在于, 执行器函数是同步执行(sync)的。这是因为执行器函数是期约的初始化程序。
        // 通过下面的例子可以看出上面代码的执行顺序:
        new Promise(() => setTimeout(console.log, 0, 'executor'));
        setTimeout(console.log, 0, 'promise initialized');
        // executor 
        // promise initialized 

        ///////////////////////////////////////////////////////////////////
        //添加setTimeout可以推迟切换状态:
        let p3 = new Promise((resolve, reject) => setTimeout(resolve, 1000));
        // 在console.log打印期约实例的时候, 还不会执行超时回调(即resolve())
        setTimeout(console.log, 0, p3);  // Promise <pending>  


        ///////////////////////////////////////////////////////////////////
        // 无论resolve()和reject()中的哪个被调用，状态转换都不可撤销了。于是继续修改状态会静默
        // 失败, 如下所示:
        let p4 = new Promise((resolve, reject) => {
            resolve();
            reject();   // 没有效果
        });

        setTimeout(console.log, 0, p4);

        
    </script>
</body>
</html>